package mobile.game.core.gen.observer;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;

import com.google.protobuf.Descriptors.Descriptor;

import common.msg.Options;
import mobile.game.core.gen.GenBase;
import mobile.game.core.gen.GenUtils;
import mobile.game.core.support.ClassFinder;
import mobile.game.core.support.SysException;
import mobile.game.core.support.observer.MsgReceiver;
import mobile.game.core.support.observer.Observer;



/**
 * Proxy自动生成类
 */
public class GenObserverInit extends GenBase {	
	private String genClassName = "MsgReceiverInit";
	private String ftlFileName = "MsgReceiverInit.ftl";
	private Class<? extends Annotation> annoClass = MsgReceiver.class;
	
	public GenObserverInit(String packageName, String targetDir, String genClassName, 
			String ftlFileName, Class<? extends Annotation> annoClass) throws Exception {
		super(packageName, targetDir);
		this.genClassName = genClassName;
		this.ftlFileName = ftlFileName;
		this.annoClass = annoClass;
		this.init();
	}
	
	/**
	 * 初始化class信息，是否能生成等字段
	 * @throws Exception
	 */
	private void init() throws Exception {
		/**  默认是true */
		this.canGen = true;
		
		// 遍历所有类信息, 获取模板信息, 判断是否出错, 出错则初始化异常不能生成
		Map<Class<?>, List<Method>> classes = GenUtils.findMethodNeedToGen(this.packageName, null, annoClass);
		for(Entry<Class<?>, List<Method>> entry : classes.entrySet()) {
			Class<?> clazz = entry.getKey();
			List<Method> ms = entry.getValue();
			try {
				this.rootMaps.add(getRootMap(clazz, ms));
			} catch (Exception e) {
				// 如果获取模板内容异常，表示不能生成
				/**  不能生成 */
				this.canGen = false;
				System.err.println("文件存在错误，不继续进行Observer生成了，错误如下：");
				e.printStackTrace();
			}
		}
		
	}
	
	/**
	 * 生成Proxy类的核心方法
	 * @param rootMap
	 * @throws Exception
	 */
	
	@Override
	protected void genFileHandler(Map<String, Object> rootMap) throws Exception {

	}

	
	@Override
	protected void genGlobalFileHandler(Map<String, Object> rootMaps) throws Exception {
		rootMaps.put("rootClassName", genClassName);
		
		String targetFileDir = this.targetDir + ClassFinder.packageToPath(this.packageName) + "/";
		writeFile(targetFileDir, genClassName + ".java", ftlFileName, rootMaps);
	}
	
	/**
	 * 根据Class及其methods获取填充模板内容
	 * @param clazz
	 * @param methods
	 * @return
	 * @throws Exception 
	 */
	private Map<String, Object> getRootMap(Class<?> clazz, List<Method> methods) throws Exception {
		// 获取实体类名,表名,包名
  		String packageName = clazz.getPackage().getName();
		String className = clazz.getSimpleName();
		
	
		// 填充Map
		Map<String, Object> rootMap = new HashMap<>();
		List<Map<String, Object>> methodInfos = new ArrayList<>();
		rootMap.put("packageName", packageName);
		rootMap.put("className", className);
		rootMap.put("methods", methodInfos);
		
		// 遍历methods，获取方法名，方法参数等信息
		for (Method m : methods) {			
			// 模板所需数据
			String name = m.getName();
			String callerStr = packageName + "." + className + ":" + name;
			String paramsCall = "";
			String params = "";
			String functionTypes = "";
			boolean hasException = m.getExceptionTypes().length > 0;
						
			Map<String, Object> method = new LinkedHashMap<>();
			Map<String, Object> paramInfo = new LinkedHashMap<>();

			// 获得keys相关信息
			Set<String> keys = getListenerKey(m);
			method.put("keys", keys);

			// 获取方法形参的类型，参数名
			Parameter[] paramList = m.getParameters();
			for(int i = 0; i < paramList.length; i++) {
				/* 这边为了只保留class.getSimpleName()得到的类名，而不是类名$内部类名，用正则替换了$以前的字符为空
				 * 如com.google.protobuf.Message.Builder，Builder是Message的内部类，分别是如下结果
				 * ctclass.getSimpleName() -> Message$Builder
				 * ctclass.getSimpleName().replaceAll("^.+\\$", "") -> Builder
				 * 非内部类不影响结果
				 */
				/**  参数类型 */
				String ptype = paramList[i].getType().getCanonicalName();//.replaceAll("^.+\\$", "");
				/**  参数名 */
				String pname = paramList[i].getName();
				paramInfo.put(pname, ptype);
			}
			
			// 取出具体模板所需方法形参类型，形参名
			int j = 0;
			String callerStrTmp = "";
			for(Entry<String, Object> info: paramInfo.entrySet()) {
				String pname = info.getKey();
				String ptype = (String) info.getValue();
				
				if(j > 0){
					params += ", "; 	
					callerStrTmp += ", ";
					paramsCall += ", ";
					functionTypes += ", ";
				}

				callerStrTmp += ptype;
				paramsCall += pname;
				params += ptype.replaceAll("\\[\\]", "...") + " " + pname;
				functionTypes += GenUtils.primitiveTowrapper(ptype);
				
				j ++;
			}
			
			callerStr += "(" + callerStrTmp + ")";
			
			if(StringUtils.isNotBlank(functionTypes)) {
				functionTypes = "<" + functionTypes + ">";
			}
			
			method.put("name", name);
			method.put("params", params);
			method.put("hasException", hasException);
			method.put("callerStr", callerStr);
			method.put("paramsCall", paramsCall);
			method.put("functionTypes", functionTypes);
			method.put("paramsSize", j);
			
			//生成方法名对应的Enum常量
			String enumCall = callerStr.replace("()", "").replace("[]", "s").replaceAll("[.:(,]", "_").replaceAll("[ )]", "").toUpperCase();
			method.put("enumCall", enumCall);
			method.put("enumCallHashCode", String.valueOf(enumCall.hashCode())) ;
			
			methodInfos.add(method);
		}
		
		return rootMap;
	}
	
	/**
	 * 获取被监听事件的关键字
	 * @param method
	 * @return
	 * @throws Exception
	 */
	private Set<String> getListenerKey(Method method) throws Exception {
		Annotation anno = (Annotation)method.getAnnotation(annoClass);
		Method mKey = annoClass.getMethod("value");
		Object oKey = mKey.invoke(anno);
		Object[] vKey;
		if(oKey instanceof int[]) {
			int[] keys = (int[])oKey;
			vKey = new Object[keys.length];
			for(int i = 0; i < keys.length; ++i) {
				vKey[i] = keys[i];
			}
		} 
		else if (oKey instanceof Class) {
			Class<?> cl = (Class<?>)oKey;
			Object object = cl.getMethod("getDescriptor").invoke(null);
			Descriptor descriptor = (Descriptor)(object);
			int msgid = descriptor.getOptions().getExtension(Options.myOption);
			
			vKey = new Object[1];
			vKey[0] = msgid;
		}
		else {
			vKey = (Object[])oKey;
		}
		
		// 获取注解设置的主事件
		String[] vSubStr = {};
		int[] vSubInt = {};
		long[] vSubLong = {};
		for(Method m : annoClass.getMethods()) {
			String mName = m.getName();
			if("subStr".equals(mName)) {
				vSubStr = (String[])m.invoke(anno);
			} else if("subInt".equals(mName)) {
				vSubInt = (int[])m.invoke(anno);
			} else if("subLong".equals(mName)) {
				vSubLong = (long[])m.invoke(anno);
			}
		}
		
		// 多个子事件key，只容许设置一个，这里检查下
		int vSubCount = 0;
		if(vSubStr.length > 0) {
			vSubCount++;
		}
		if(vSubInt.length > 0) {
			vSubCount++;
		}
		if(vSubLong.length > 0) {
			vSubCount++;
		}
		
		// 设置监听了多个不同类型的子事件
		if(vSubCount > 1) {
			throw new SysException("Observer监听参数设置错误，不允许同时设置多种不同参数类型的子事件："
					+ "method={}, anno={}, subStr={}, subInt={}, subLong={}",
					method, anno, vSubStr, vSubInt, vSubLong);
		}
		
		// 获取子类型设置
		String[] vSubKey = getSubKeysFromValue(vSubStr, vSubInt, vSubLong);
		
		// 监听关键字
		Set<String> results = new HashSet<>();
		
		// 需要监听的事件关键字
		for(Object k : vKey) {
			// 如果主事件和子事件都有多个，那么会出现乘积，暂时先允许
			for(String sk : vSubKey) {
				String smk = Observer.createMethodKey(k, sk);
				results.add(smk);
			}
			
			// 如果没有设置子事件，那么就生成主事件
			if(vSubKey.length == 0) {
				String smk = Observer.createMethodKey(k, null);
				results.add(smk);
			}
		}
		
		return results;
	}
	
	private static String[] getSubKeysFromValue(String[] vSubStr, int[] vSubInt, long[] vSubLong) {
		String[] vSubKey = {};
		
		if(vSubStr.length > 0) {
			vSubKey = new String[vSubStr.length];
			for(int i = 0; i < vSubStr.length; ++i) {
				vSubKey[i] = vSubStr[i];
			}
		} else if(vSubInt.length > 0) {
			vSubKey = new String[vSubInt.length];
			for(int i = 0; i < vSubInt.length; ++i) {
				vSubKey[i] = String.valueOf(vSubInt[i]);
			}
		} else if(vSubLong.length > 0) {
			vSubKey = new String[vSubLong.length];
			for(int i = 0; i < vSubLong.length; ++i) {
				vSubKey[i] = String.valueOf(vSubLong[i]);
			}
		}
		
		return vSubKey;
	}
	
	public static void main(String[] args) throws Exception {
		if(args.length < 2) {
			System.out.println("usage: packageName, targetDir");
			return;
		}
		
		//设置log4j2配置文件所需的环境变量，作用是gen的时候
		//不会报配置没找到的错误，同时有gen.log的日志记录
		System.setProperty("logFileName", "GenObserverInit");
		
		String packageName = args[0];
		String targetDir = System.getProperty("user.dir") + args[1];
		
		GenBase genMsgReceiverInit = new GenObserverInit(packageName, targetDir, "MsgReceiverInit", "ObserverInit.ftl", MsgReceiver.class);
//		GenBase genListenerInit = new GenObserverInit(packageName, targetDir, "ListenerInit", "ObserverInit.ftl", Listener.class);
		
		if(!genMsgReceiverInit.isCanGen()){
			System.err.println("不能生成MsgReceiverInit，请检查重试。。");
			return ;
		}
		
//		if(!genListenerInit.isCanGen()){
//			System.err.println("不能生成ListenerInit，请检查重试。。");
//			return ;
//		}
		
		genMsgReceiverInit.genFiles();
//		genListenerInit.genFiles();
	}
}

